package ws

import (
	"errors"
	"sync"

	"github.com/gorilla/websocket"
)

type Connection struct {
	wsConn    *websocket.Conn //websocket连接
	inChan    chan []byte     //接收的消息
	outChan   chan []byte     //发送的消息
	closeChan chan byte       //提醒其他协程该连接已关闭
	isClose   bool            //保证closeChan只关闭一次
	mutex     sync.Mutex
}

//将连接封装
func InitConnection(wsConn *websocket.Conn) (conn *Connection, err error) {
	conn = &Connection{
		wsConn:    wsConn,
		inChan:    make(chan []byte, 1000), //可以接收1000个消息
		outChan:   make(chan []byte, 1000), //可以发1000个消息
		closeChan: make(chan byte, 1),
	}

	//启动读协程，如果有数据就放到inChan队列里
	//通过循环读数据
	go conn.readLoop()

	//启动写协程，如果outChan队列有数据就发送消息
	//通过循环写数据
	go conn.writeLoop()
	return
}

//对外暴露的读接口
func (conn *Connection) ReadMessage() (data []byte, err error) {
	select {
	case data = <-conn.inChan:
	case <-conn.closeChan: //如果连接已经关闭了就返回错误
		err = errors.New("connection is closed")
	}
	return
}

//对外暴露的写接口
func (conn *Connection) WriteMessage(data []byte) (err error) {
	select {
	case conn.outChan <- data:
	case <-conn.closeChan:
		err = errors.New("connection is closed")
	}
	return
}

//Close 关闭连接
func (conn *Connection) Close() {
	//close方法是线程安全的，可以多次并发调用(可重入)
	conn.wsConn.Close()

	//加锁保证线程安全，防止同时读取isclose然后同时关闭closeChan
	conn.mutex.Lock()
	if !conn.isClose { //isClose为false时执行，保证closeChan只关闭一次
		//关闭closeChan，表示连接已经关闭，用来提醒readLoop和writeLoop
		close(conn.closeChan)
		conn.isClose = true
	}
	conn.mutex.Unlock()
}

//内部读写实现

//readLoop 读数据，如果有数据就会加入inChan，通过ReadMessage接口就可以读到数据
func (conn *Connection) readLoop() {

	var (
		data []byte
		err  error
	)

	for {
		if _, data, err = conn.wsConn.ReadMessage(); err != nil {
			goto ERR //如果读错误就关闭底层连接
		}
		// //读成功了就把消息放进inChan队列里
		// //如果没有人读数据，队列满了就会阻塞在这里，如果刚好写数据时报错连接断开了，此时协程就会卡在这里，因为他不知道连接已经关闭了
		// conn.inChan <- data

		//使用select来监听信道，如果closeChan有数据了就表示连接关闭了，此时就退出循环
		select {
		case conn.inChan <- data:
		case <-conn.closeChan:
			goto ERR
		}
	}

ERR:
	conn.Close()
}

//writeLoop 写数据，如果outChan有数据就发送，通过WriteMessage接口就可以发送数据
func (conn *Connection) writeLoop() {

	var (
		data []byte
		err  error
	)

	for {
		select {
		//从写队列里读取数据并发送
		case data = <-conn.outChan:
		case <-conn.closeChan:
			goto ERR
		}

		if err = conn.wsConn.WriteMessage(websocket.TextMessage, data); err != nil {
			goto ERR
		}
		//发送成功就继续循环

	}

ERR:
	conn.Close()
}
